package com.hzw.saas.common.open.controller;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.config.util.RedisUtil;
import com.hzw.saas.common.open.pojo.dto.TokenInfoDto;
import com.hzw.saas.common.open.pojo.model.SaasClient;
import com.hzw.saas.common.open.service.ISaasClientService;
import com.hzw.saas.common.security.constants.SecurityConstants;
import com.hzw.saas.common.security.pojo.dto.SaasUser;
import com.hzw.saas.common.security.token.SaasTokenService;
import io.swagger.annotations.*;
import jodd.util.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * <p>
 * 第三方授权接口
 * </p>
 * 1、申请APPID。
 * 2、登录模式方式为授权码模式登录（调用/authorize）获取授权码。
 * 3、在后台通过授权码(授权码模式)拿到access_token，这样就可以访问saas后台接口。
 * 4、如果access_token快过期了，则调用刷新token模式，刷新access_token，或者重新授权。
 *
 * @author sonam
 * @since 2021/3/1 9:38
 */
@Slf4j
@Deprecated
@RestController
@Api(tags = "（旧）应用接入/OAuth2认证接口")
@RequestMapping("/saas/oauth2")
@RequiredArgsConstructor
public class OldOauth2Controller {
    private final static int ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60 * 12; // access_token默认有效期

    @Resource(name = "saasClientServiceImpl")
    private ISaasClientService saasClientService;
    private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;
    private final SaasTokenService saasTokenService;
    private final AuthorizationServerTokenServices userTokenServices;

    @RequestMapping(value = "/info/{client_id}", method = RequestMethod.GET)
    @ApiOperation(value = "站点基本信息")
    @ApiOperationSort(1)
    public ResponseEntity<Object> info(@ApiParam("客户端id") @PathVariable String client_id) {

        SaasClient client = getClient(client_id);

        Map<String, Object> map = new HashMap<>(16);
        map.put("name", client.getName());
        map.put("homeUri", client.getHomeUri());
        map.put("autoApprove", client.getAutoApprove());
        return ResponseEntity.ok(map);
    }

    @RequestMapping(value = "/authorize", method = RequestMethod.POST)
    @ApiOperation(value = "认证获取code", notes = "授权码模式认证:\n" +
        "1、AppId。\n" +
        "2、登录模式方式为授权码模式登录（调用/authorize）获取授权码。\n" +
        "3、在后台通过授权码(授权码模式)拿到access_token，这样就可以访问saas后台接口。\n" +
        "4、如果access_token快过期了，则调用刷新token模式，刷新access_token，或者重新授权。")
    @ApiResponse(code = 200, message = "返回授权码code")
    @ApiOperationSort(2)
    public ResponseEntity<com.hzw.saas.open.pojo.dto.OldCodeInfoDto> authorize(@ApiParam(value = "用户名", required = true) @RequestParam String username,
                                                                               @ApiParam(value = "密码", required = true) @RequestParam String password,
                                                                               @ApiParam(value = "客户端id", required = true) @RequestParam String client_id,
                                                                               @ApiParam(value = "成功授权后回调地址", required = true) @RequestParam String redirect_uri) {
        SaasClient client = getClient(client_id);
        AssertUtil.assertThrow("回调地址redirect_uri不正确", !Objects.equals(client.getRedirectUri(), redirect_uri));
        OAuth2AccessToken oAuth2AccessToken = doLogin(username, password);
        /** 生成code 5分钟内有效*/
        String code = IdUtil.simpleUUID();
        /** 减授权码与accessToken关联*/
        com.hzw.saas.open.pojo.dto.OldCodeInfoDto codeInfo = new com.hzw.saas.open.pojo.dto.OldCodeInfoDto();
        codeInfo.setClientId(client_id);
        codeInfo.setCode(code);
        codeInfo.setUsername(username);
        codeInfo.setAccessToken(oAuth2AccessToken.getValue());
        /** 5min有效*/
        RedisUtil.set(SecurityConstants.SAAS_OAUTH_PREFIX + code, codeInfo, 5 * 60);
        return ResponseEntity.ok(codeInfo);
    }

    @RequestMapping(value = "/token", method = RequestMethod.GET)
    @ApiOperation(value = "获取access_token令牌",
        notes = "该接口有3种模式，一种授权码模式 /saas/oauth2/token?grant_type=authorization_code&client_id=&client_secret=&redirect_uri=," +
            "二种密码模式 /saas/oauth2/token?grant_type=password&client_id=&username=&password=" +
            "最后一种刷新token模式 /saas/oauth2/token?grant_type=refresh_token&client_id=&client_secret=")
    @ApiOperationSort(3)
    public ResponseEntity<TokenInfoDto> token(@ApiParam(value = "授权类型,（授权码模式 authorization_code | 密码模式 password | 刷新token（refresh_token））", defaultValue = "password")
                                              @RequestParam(defaultValue = "password") String grant_type,
                                              @ApiParam(value = "用户名(密码模式需要)") @RequestParam(required = false) String username,
                                              @ApiParam(value = "用户密码(密码模式需要)") @RequestParam(required = false) String password,
                                              @ApiParam(value = "客户端id", required = true) @RequestParam String client_id,
                                              @ApiParam(value = "客户端秘钥(密码模式不需要)") @RequestParam(required = false) String client_secret,
                                              @ApiParam(value = "认证返回的code(授权模式需要)") @RequestParam(required = false) String code,
                                              @ApiParam(value = "刷新token(刷新token模式需要)") @RequestParam(required = false) String refresh_token,
                                              @ApiParam(value = "成功授权后回调地址(密码模式不需要)") @RequestParam(required = false) String redirect_uri) {
        SaasClient client = getClient(client_id);

        if(!"password".equals(grant_type)) { // 密码模式不需要回调
            AssertUtil.assertThrow("client_secret不正确", !client.getClientSecret().equals(client_secret));
        }

        OAuth2AccessToken oAuth2AccessToken;
        if ("authorization_code".equals(grant_type)) {
            AssertUtil.assertThrow("回调地址redirect_uri不正确",
                !Objects.equals(StringUtil.suffix(client.getRedirectUri(), StrUtil.SLASH), StringUtil.suffix(redirect_uri, StrUtil.SLASH)));
            // 通过授权码获取 token
            com.hzw.saas.open.pojo.dto.OldCodeInfoDto tokenInfo = RedisUtil.get(SecurityConstants.SAAS_OAUTH_PREFIX + code);
            AssertUtil.assertThrow("授权码已过期", Objects.isNull(tokenInfo));
            AssertUtil.assertThrow("授权码不正确", !Objects.equals(tokenInfo.getClientId(), client_id));
            String accessToken = tokenInfo.getAccessToken();
            oAuth2AccessToken = saasTokenService.readAccessToken(accessToken);
        } else if ("refresh_token".equals(grant_type)) {
            // 刷新token
            TokenRequest tokenRequest = new TokenRequest(null, null, null, null);
            oAuth2AccessToken = userTokenServices.refreshAccessToken(refresh_token, tokenRequest);
        } else if ("password".equals(grant_type)) {
            // 密码模式
            oAuth2AccessToken = doLogin(username, password);
        } else {
            throw DefaultSaasException.build("授权类型grant_type不正确").status(HttpStatus.BAD_REQUEST);
        }

        TokenInfoDto tokenInfo = new TokenInfoDto();
        tokenInfo.setAccessToken(oAuth2AccessToken.getValue());
        tokenInfo.setExpiresIn(oAuth2AccessToken.getExpiresIn());
        if(Objects.nonNull(oAuth2AccessToken.getRefreshToken())) {
            tokenInfo.setRefreshToken(oAuth2AccessToken.getRefreshToken().getValue());
        }
        return ResponseEntity.ok(tokenInfo);
    }

    private OAuth2AccessToken doLogin(String username, String password) {
        /** 登录认证（模拟登录）*/
        SaasUser user = (SaasUser) userDetailsService.loadUserByUsername(username);
        AssertUtil.assertThrow("用户名不存在", Objects.isNull(user));
        AssertUtil.assertThrow("用户或密码不正确", !passwordEncoder.matches(password, user.getPassword()));
        /** 创建accessToken*/
        Object principal = user;
        Object credentials = user.getPassword();
        UsernamePasswordAuthenticationToken authentication =
            new UsernamePasswordAuthenticationToken(principal, credentials, user.getAuthorities());
        TokenRequest tokenRequest = new TokenRequest(null, null, Collections.singletonList("all"), null);
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(new BaseClientDetails());
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
        return saasTokenService.createAccessToken(oAuth2Authentication);
    }

    private SaasClient getClient(String clientId) {
        SaasClient client = saasClientService.getById(clientId);
        AssertUtil.assertThrow("客户端client_id不存在", Objects.isNull(client));
        return client;
    }

}
